package com.qtrue.string;

import org.junit.Test;

/**
 * @Package: com.qtrue.string
 * @ClassName: StringBasic
 * @Author: xiaoBao
 * @CreateTime: 2022-05-16 16:40
 * @Description:String类的使用
 */
public class StringBasic {
    /*
        String字符串：使用一串""引起来表示（在java中随便使用 双引号""括起来 的都是String对象）
            例如：“abc”，“def”，“hello world!”，这是3个String对象

        1. String类被final关键字修饰，不可以被继承。

        2. String实现了Serializable接口：表示字符串是支持可序列化的。
                 实现了Comparable接口：表示String可以比较大小

        3. String内部定义了final char[] value用于存储字符串数据（被final修饰的char型数组）

        4. String：代表不可变的字符序列。简称：不可变性（双引号括起来的字符串，是 不可变 的）
            例如：声明一个字符串"abc"，它自出生到最终死亡，不可变，不能变成"abcd"，也不能变成"ab"。
            4.1 当对字符串重新赋值时，需要重新指定内存区域赋值，不能使用原有的value进行赋值。
            4.2 当对现有的字符串进行连接操作时，也需要重新指定内存区域赋值，不能使用原有的value赋值。
            4.3 当调用String的replace()修改指定的字符时，也必须重新指定内存区域进行赋值。
            总结：因为String内部存储字符串使用的是final修饰的常量，只要对字符串的内容进行任何的修改，都必须重新造，原有的不动，这就叫“不可变性”、

        5. 在JDK当中双引号括起来的字符串，例如：“abc” "def"都是直接存储在“方法区”的“字符串常量池”当中的

        6. 通过字面量的方式（区别于new）
    */

    public static void main(String[] args) {
        String s1 = "abc"; // 字面量的定义方式：不通过new来声明一个字符串并赋值
        // "abc"是直接存储在方法区的字符串常量池当中
        // 所以下面这个"abc"不会新建（因为这个对象已经存在了）
        String s2 = "abc";

        // == 比较的是变量中保持的内存地址
        System.out.println(s1 == s2); // true

        // new对象的时候一定在堆内存当中开辟空间
        // 凡是双引号括起来的都会在字符串常量池中保存有一份
        String s3 = new String("xy");
        String s4 = new String("xy");

        // == 比较的是变量中保持的内存地址
        System.out.println(s3 == s4); // false

        String s5 = "abc";
        String s6 = s5.replace("a","m");
        System.out.println(s5); // abc
        System.out.println(s6); // mbc
    }

    /*
        String的实例化方式：
            方式一：通过字面量定义的方式
            方式二：通过 new + 构造器 的方式

        面试题：String s = new String("abc");方式创建对象，在内存中创建了几个对象？
            两个：一个是堆空间中new结构创建的对象，另一个是char[]型数组对应的常量池中的数据："abc"。
    */
    @Test
    public void StringTest02(){
        // 通过字面量定义的方式：此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中
        String s1 = "javaEE";
        String s2 = "javaEE";

        // 通过 new + 构造器 的方式：此时的s3和s4保存的地址值，是数据在堆空间开辟空间以后对应的地址值。
        String s3 = new String("javaEE");
        String s4 = new String("javaEE");

        System.out.println(s1 == s2); // true
        System.out.println(s3 == s4); // false

        System.out.println("**********************");

        Person p1 = new Person("Tom",12);
        Person p2 = new Person("Tom",12);

        System.out.println(p1.name.equals(p2.name)); // true
        System.out.println(p1.name == p2.name); // true：原因是这两个实例化对象的name都存在同一个地址值，通过字面量方式定义的字符串数据都存储在常量池中。

        p1.name = "Jerry";
        // 输出Tom：虽然上面通过p1.name修改了同一个地址值的字符串数据，但因为字符串的“不可变性”它依然是Tom，无法改变最先定义的数据。
        System.out.println(p2.name);
    }

    @Test
    public void StringTest03(){
        String s1 = "javaEE";
        String s2 = "hadoop";

        String s3 = "javaEEhadoop";
        String s4 = "javaEE" + "hadoop";
        String s5 = s1 + "hadoop";
        String s6 = "javaEE" + s2;
        String s7 = s1 + s2;

        /*
        结论：
            1. 常量与常量的拼接结果存储在常量池，且常量池中不会存在相同内容的常量。因此，s3 == s4为true
            2. 只要字符串的存储其中有一个变量参与，那么结果就存储在堆中。
            3. 如果拼接的结果调用的inter()，返回值就在常量池中。
        */
        System.out.println(s3 == s4); // true
        System.out.println(s3 == s5); // false
        System.out.println(s3 == s6); // false
        System.out.println(s3 == s7); // false
        System.out.println(s5 == s6); // false
        System.out.println(s5 == s7); // false
        System.out.println(s6 == s7); // false

        String s8 = s5.intern(); // 返回值会使用常量池中已存在的"javaEEhadoop"，如果常量池中不存在也会先放到常量池中再返回。
        System.out.println(s3 == s8); // true
    }
}
